using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

namespace UnityTest.IntegrationTestRunner
{
	class IntegrationTestsProvider
	{
		internal Dictionary<ITestComponent, HashSet<ITestComponent>> testCollection = new Dictionary<ITestComponent, HashSet<ITestComponent>> ();
		internal ITestComponent currentTestGroup;
		internal IEnumerable<ITestComponent> testToRun;

		public IntegrationTestsProvider ( IEnumerable<ITestComponent> tests )
		{
			testToRun = tests;
			foreach (var test in tests.OrderBy (component => component))
			{
				if (test.IsTestGroup ())
				{
					throw new Exception (test.Name + " is test a group");
				}
				AddTestToList (test);
			}
			if (currentTestGroup == null)
			{
				currentTestGroup = FindInnerTestGroup (TestComponent.NullTestComponent);
			}
		}

		private void AddTestToList (ITestComponent test)
		{
			var group = test.GetTestGroup ();
			if (!testCollection.ContainsKey (group)) 
				testCollection.Add (group, new HashSet<ITestComponent> ());
			testCollection[group].Add (test);
			if (group == TestComponent.NullTestComponent) return;
			AddTestToList (group);
		}

		public ITestComponent GetNextTest ()
		{
			var test = testCollection[currentTestGroup].First ();
			testCollection[currentTestGroup].Remove (test);
			test.EnableTest(true);
			return test;
		}
		
		public void FinishTest (ITestComponent test)
		{
			try
			{
				test.EnableTest (false);
				currentTestGroup = FindNextTestGroup (currentTestGroup);
			}
			catch (MissingReferenceException e)
			{
				Debug.LogException(e);
				return;
			}
		}

		private ITestComponent FindNextTestGroup ( ITestComponent testGroup )
		{
			if (testGroup != null)
			{
				if (testCollection[testGroup].Any ())
				{
					testGroup.EnableTest (true);
					return FindInnerTestGroup (testGroup);
				}
				testCollection.Remove (testGroup);
				testGroup.EnableTest (false);

				var parentTestGroup = testGroup.GetTestGroup ();
				if (parentTestGroup == null) return null;

				testCollection[parentTestGroup].Remove (testGroup);
				return FindNextTestGroup(parentTestGroup);
			}
			throw new Exception ("No test left");
		}

		private ITestComponent FindInnerTestGroup ( ITestComponent group )
		{
			var innerGroups = testCollection[group];
			foreach (var innerGroup in innerGroups)
			{
				if (!innerGroup.IsTestGroup()) continue;
				innerGroup.EnableTest(true);
				return FindInnerTestGroup (innerGroup);
			}
			return group;
		}

		public bool AnyTestsLeft ()
		{
			return testCollection.Count != 0;
		}

		public List<ITestComponent> GetRemainingTests ()
		{
			var remainingTests = new List<ITestComponent> ();
			foreach (var test in testCollection)
			{
				remainingTests.AddRange (test.Value);
			}
			return remainingTests;
		}
	}
}
